package com.yookey.common.response;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Throwables;
import com.yookey.common.constant.ResponseMessageEnum;
import com.yookey.common.context.AccountUtils;
import com.yookey.common.exception.BusinessException;
import com.yookey.common.exception.ParamException;
import com.yookey.common.utils.ResponseMessageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;

@Slf4j
@ControllerAdvice(basePackages = "com.yookey")
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        String returnTypeName = returnType.getGenericParameterType().getTypeName();
        return (!returnTypeName.equals(R.class.getName())) && (!returnTypeName.equals(ResponseEntity.class.getName()));
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
                                  ServerHttpResponse response) {
        String returnTypeName = returnType.getGenericParameterType().getTypeName();
        clearLocalThread();
        if (returnTypeName.equals(Void.TYPE.getName())) {
            return R.ok(null);
        }
        if (!selectedContentType.includes(MediaType.APPLICATION_JSON)) {
            return body;
        }
        if (returnTypeName.equals(R.class.getName())) {
            return R.ok(body);
        }
        /*
         * if (body.getClass().isAssignableFrom(PageImpl.class)) { return R.ok(body); }
         */

        return body;
    }

    @ResponseBody
    @ExceptionHandler({Throwable.class})
    public <T> ResponseEntity<R> handleException(Throwable e) {
        log.error(Throwables.getStackTraceAsString(e));
        clearLocalThread();
        return ResponseEntity.status(HttpStatus.OK).body(R.error(e.getMessage()));
    }

    @ResponseBody
    @ExceptionHandler({ParamException.class})
    public <T> ResponseEntity<R> handleException(ParamException e) {
        log.error(Throwables.getStackTraceAsString(e));
        clearLocalThread();
        return ResponseEntity.status(HttpStatus.OK).body(R.fail(e.getCode(),e.getMessage()));
    }
    @ResponseBody
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public <T> ResponseEntity<R> handleException(HttpMessageNotReadableException e) {
        log.error(Throwables.getStackTraceAsString(e));
        clearLocalThread();
        return ResponseEntity.status(HttpStatus.OK).body(R.fail(ResponseMessageEnum.PARAMETRIC_ERROR));
    }

    /**
     * 参数缺失
     * @param e
     * @param <T>
     * @return
     */
    @ResponseBody
    @ExceptionHandler({MissingServletRequestParameterException.class})
    public <T> ResponseEntity<R> handleException(MissingServletRequestParameterException e) {
        log.error(Throwables.getStackTraceAsString(e));
        clearLocalThread();
        return ResponseEntity.status(HttpStatus.OK).body(R.fail(ResponseMessageEnum.PARAMETRIC_ERROR));
    }
    @ResponseBody
    @ExceptionHandler({ConstraintViolationException.class})
    public <T> ResponseEntity<R> handleException(ConstraintViolationException e) {
        log.error(Throwables.getStackTraceAsString(e));
        clearLocalThread();
        String message = e.getConstraintViolations().iterator().next().getMessage();
        return ResponseEntity.status(HttpStatus.OK).body(R.fail(ResponseMessageEnum.PARAMETRIC_ERROR.getMessageKey(), message));
    }
    @ResponseBody
    @ExceptionHandler(value = {BindException.class, MethodArgumentNotValidException.class})
    public Object validationExceptionHandler(Exception exception) {
        clearLocalThread();
        BindingResult bindResult = null;
        if (exception instanceof BindException) {
            bindResult = ((BindException) exception).getBindingResult();
        } else if (exception instanceof MethodArgumentNotValidException) {
            bindResult = ((MethodArgumentNotValidException) exception).getBindingResult();
        }
        String msg;
        if (bindResult != null && bindResult.hasErrors()) {
            msg = bindResult.getAllErrors().get(0).getDefaultMessage();
            if (msg.contains("NumberFormatException")) {
                msg = "参数类型错误！";
            }
        }else {
            msg = "系统繁忙，请稍后重试...";
        }
        return ResponseEntity.status(HttpStatus.OK).body(R.fail(ResponseMessageEnum.PARAMETRIC_ERROR.getMessageKey(), msg));
    }

    /**
     * 未知的异常捕获处理
     * @param exception
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = NullPointerException.class)
    public Object nullPointerExceptionHandler(HttpServletRequest request, Exception exception) {
        logError(request, exception);
        clearLocalThread();
        return ResponseEntity.status(HttpStatus.OK).body(R.fail(ResponseMessageEnum.NULL_ERROR.getMessageKey(), ResponseMessageUtil.getMessage(ResponseMessageEnum.NULL_ERROR)));
    }

    /**
     * 未知的异常捕获处理
     * @param exception
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Object allUnknowExceptionHandler(HttpServletRequest request, Exception exception) {
        logError(request, exception);
        clearLocalThread();
        return ResponseEntity.status(HttpStatus.OK).body(R.fail(ResponseMessageEnum.UNKNOWN_ERROR.getMessageKey(), exception.toString()));
    }

    private String logError(HttpServletRequest request, Exception exception) {
        log.error("发生未知异常:", exception);
        StringWriter sw = new StringWriter();
        sw.append(String.format("Date:{%s};%n", DateUtil.formatDate(new Date()).toString()));
        sw.append(String.format("url:{%s}产生错误;%n", request.getRequestURI()));
        sw.append(String.format("请求IP:{%s};%n", request.getRemoteAddr()));
        sw.append(String.format("type:{%s};%n", request.getMethod()));
        sw.append(String.format("请求参数:{%s};%n", JSONObject.toJSONString(request.getParameterMap())));
        exception.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }
    @ResponseBody
    @ExceptionHandler({BusinessException.class})
    public <T> ResponseEntity<R> handleException(BusinessException e) {
        log.error(Throwables.getStackTraceAsString(e));
        clearLocalThread();
        String errCode = e.getCode();
        String message = e.getMessage();
        if (StringUtils.isBlank(errCode)) {
            errCode = ResponseMessageEnum.UNKNOWN_ERROR.getMessageKey();
        }
        if (StringUtils.isBlank(message)) {
            message = ResponseMessageUtil.getMessage(errCode, e.getParams());
        }

        R respEntity = R.fail(e.getCode(), message);
        if (e.getHttpStatus() != null) {
            return ResponseEntity.status(e.getHttpStatus()).body(respEntity);
        } else {
            return ResponseEntity.status(HttpStatus.OK).body(respEntity);
        }
    }

    private void clearLocalThread(){

        if (AccountUtils.get()!=null) {
            AccountUtils.remove();
        }
    }
}
